﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Kyoh.App.Crawler.Contents;
using Kyoh.App.Crawler.Plugin;
using Kyoh.Lib.Component;
using Kyoh.Lib.Component.Traces;
using Kyoh.Lib.WinForms;

namespace Kyoh.App.Crawler
{
    partial class MainForm : FormPlus
    {
        private Parsers parsers;
        private Redirects redirects;
        private Seekers seekers;
        private Pickers pickers;

        public MainForm()
            : base(Properties.Settings.Default)
        {
            using (var splash = new SplashForm())
            {
                splash.Show(this);
                splash.InitComponents();

                InitializeComponent();

                outputDataGridView.DataSource = tracer;

                // ローダー一覧の列を設定
                loadersDataGridView.AutoGenerateColumns = false;
                loadersDataGridView.DataSource = loaders;
                GenerateLoadersDataGridViewColumns();

                // アンカー一覧の列を設定
                anchorsDataGridView.DataSource = anchors;

                // 出力の選択
                outputEventTypeFilterToolStripBindingComboBox.SelectedIndex = -1;

                anchors.Parsers =
                parsers = splash.Parsers;
                anchors.Redirects =
                redirects = splash.Redirects;
                anchors.Seekers =
                seekers = splash.Seekers;

                pickers = splash.Pickers;
                pickersPluginListMenu.DataSource = pickers;

                splash.Close();
            }
        }

        #region DataGridView デザイン

        private void GenerateLoadersDataGridViewColumns()
        {
            loadersDataGridView.Columns.Clear();
            var format = new UnitFormatInfo(CultureInfo.InvariantCulture, UnitPrefix.SiMatric, true, false, "N UB");

            loadersDataGridView.RowTemplate.Height = decimal.ToInt32(Properties.Settings.Default.LoaderRowSize);

            DataGridViewColumn column = new DataGridViewImageColumn();
            column.ReadOnly = true;
            column.HeaderText = Properties.DisplayName.StatusImage;
            column.DataPropertyName = LoaderProperties.PropertyNameStatusImage;
            loadersDataGridView.Columns.Add(column);

            column = new DataGridViewTextBoxColumn
                         {
                             ReadOnly = true,
                             HeaderText = Properties.DisplayName.Title,
                             DataPropertyName = LoaderProperties.PropertyNameTitle
                         };
            loadersDataGridView.Columns.Add(column);

            column = new DataGridViewComboBoxColumn
                         {
                             ReadOnly = false,
                             HeaderText = Properties.DisplayName.Priority,
                             DataPropertyName = LoaderProperties.PropertyNamePriority
                         };
            ((DataGridViewComboBoxColumn)column).DataSource = Enum.GetValues(typeof(SeedPriority));
            ((DataGridViewComboBoxColumn)column).FlatStyle = FlatStyle.Flat;
            loadersDataGridView.Columns.Add(column);

            column = new DataGridViewProgressBarColumn
                         {
                             ReadOnly = true,
                             HeaderText = Properties.DisplayName.ProgressPercentage,
                             DataPropertyName = LoaderProperties.PropertyNameProgressPercentage,
                             DefaultCellStyle = {Padding = new Padding(2), ForeColor = Color.Black}
                         };
            ((DataGridViewProgressBarColumn)column).DisplayText = true;
            ((DataGridViewProgressBarColumn)column).FlatStyle = FlatStyle.Flat;
            loadersDataGridView.Columns.Add(column);

            column = new DataGridViewTextBoxColumn
                         {
                             ReadOnly = true,
                             HeaderText = Properties.DisplayName.BytesReceived,
                             DataPropertyName = LoaderProperties.PropertyNameBytesReceived
                         };
            {
                var style = column.DefaultCellStyle;
                style.Format = "0.00";
                style.FormatProvider = format;
            }
            loadersDataGridView.Columns.Add(column);

            column = new DataGridViewTextBoxColumn
                         {
                             ReadOnly = true,
                             HeaderText = Properties.DisplayName.TotalBytesToReceive,
                             DataPropertyName = LoaderProperties.PropertyNameTotalBytesToReceive
                         };
            {
                var style = column.DefaultCellStyle;
                style.Format = "0.00";
                style.FormatProvider = format;
            }
            loadersDataGridView.Columns.Add(column);

            column = new DataGridViewTextBoxColumn
                         {
                             ReadOnly = true,
                             HeaderText = Properties.DisplayName.PageUri,
                             DataPropertyName = LoaderProperties.PropertyNameUri
                         };
            loadersDataGridView.Columns.Add(column);

            column = new DataGridViewTextBoxColumn
                         {
                             ReadOnly = true,
                             HeaderText = Properties.DisplayName.ContentUri,
                             DataPropertyName = LoaderProperties.PropertyNameContentUri
                         };
            loadersDataGridView.Columns.Add(column);

            column = new DataGridViewImageColumn
            {
                ReadOnly = true,
                HeaderText = Properties.DisplayName.Thumbnail,
                DataPropertyName = LoaderProperties.PropertyNameThumbnail,
                ImageLayout = DataGridViewImageCellLayout.Zoom,
            };
            loadersDataGridView.Columns.Add(column);
            column = new DataGridViewTextBoxColumn
            {
                ReadOnly = true,
                HeaderText = Properties.DisplayName.Summary,
                DataPropertyName = LoaderProperties.PropertyNameSummary,
            };
            loadersDataGridView.Columns.Add(column);
        }

        #endregion

        #region アンカーとローダーの追加・削除

        private void AddAnchor(IPlugin plugin)
        {
            var picker = plugin as IPicker;
            if (picker == null)
                return;

            var anchor = picker.GetAnchor(redirects);
            if (anchor != null)
                anchors.Add(new Anchor(anchor));
        }

        private void RemoveAnchor()
        {
            var row = anchorsDataGridView.SelectedRows.Cast<DataGridViewRow>().FirstOrDefault();
            if (row != null)
            {
                var anchor = row.DataBoundItem as Anchor;
                if (anchor != null)
                    anchors.Remove(anchor);
            }
        }

        private void AddLoader()
        {
            using (var form = new LoaderForm())
            {
                if (form.ShowDialog(this) != DialogResult.OK)
                    return;
                var uri = form.Uri;
                if (string.IsNullOrEmpty(uri))
                {
                    Trace.TraceInformation(Properties.Strings.InformationEmptyAnchorUri);
                    return;
                }
                redirects.ParseAsync(uri).ContinueWith(
                    tr =>
                        {
                            if (tr.IsCompleted && !tr.IsFaulted && !tr.IsCanceled)
                                parsers.ParseAsync(uri).ContinueWith(
                                    tp =>
                                        {
                                            if (tp.IsCompleted && !tp.IsFaulted && !tp.IsCanceled)
                                                loaders.Add(tp.Result);
                                            if (tp.IsFaulted)
                                            {
                                                if (tp.Exception != null)
                                                    Debug.WriteLine(tp.Exception);
                                                MessageBox.Show(this,
                                                                string.Format(Properties.Strings.ErrorParseUri,
                                                                              uri),
                                                                Properties.Strings.CaptionFailedToAddAnchor,
                                                                MessageBoxButtons.OK, MessageBoxIcon.Information,
                                                                MessageBoxDefaultButton.Button1);
                                                Trace.TraceError(Properties.Strings.ErrorParseUri, uri);
                                            }
                                        });
                            if (!tr.IsFaulted)
                                return;
                            if (tr.Exception != null)
                                Debug.WriteLine(tr.Exception);
                            MessageBox.Show(this, string.Format(Properties.Strings.ErrorRedirectUri, uri),
                                            Properties.Strings.CaptionFailedToAddAnchor, MessageBoxButtons.OK,
                                            MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
                            Trace.TraceError(Properties.Strings.ErrorRedirectUri, uri);
                        });
            }
        }

        private void ReloadLoader()
        {
            foreach (var item in loadersDataGridView.SelectedRows.Cast<DataGridViewRow>().Select(r => (Loader)r.DataBoundItem))
            {
                Loader loader = item;
                parsers.ParseAsync(item.Uri).ContinueWith(
                    t =>
                        {
                            if (t.IsCompleted && !t.IsFaulted & !t.IsCanceled)
                                loader.Refresh(t.Result);
                            if (t.IsFaulted)
                            {
                                if (t.Exception != null)
                                    Debug.WriteLine(t.Exception.ToString());
                                loader.Refresh(t.Result);
                                MessageBox.Show(this, string.Format(Properties.Strings.ErrorParseUri, loader.Uri),
                                                Properties.Strings.CaptionFailedToAddAnchor, MessageBoxButtons.OK,
                                                MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
                                Trace.TraceError(Properties.Strings.ErrorParseUri, loader.Uri);
                            }
                        });
            }
        }

        private void RemoveLoader()
        {
            var row = loadersDataGridView.SelectedRows.Cast<DataGridViewRow>().FirstOrDefault();
            if (row != null)
            {
                var loader = row.DataBoundItem as Loader;
                if (loader != null)
                    loaders.Remove(loader);
            }
        }

        #endregion

        #region コンテンツの表示

        private void RunCommand(string comm, string args)
        {
            var loaderRow = loadersDataGridView.SelectedRows
                .OfType<DataGridViewRow>()
                .FirstOrDefault();
            var loader = loaderRow == null ? null : loaderRow.DataBoundItem as Loader;

            comm = FormatFromDictionary(comm, Properties.Settings.GetParams(loader));
            args = FormatFromDictionary(args, Properties.Settings.GetParams(loader));

            try
            {
                if (string.IsNullOrWhiteSpace(args))
                    Process.Start(comm);
                else
                    Process.Start(comm, args);
            }
            catch (Exception exc)
            {
                Trace.TraceError(Properties.Strings.ErrorCommand, comm, args);
                Debug.WriteLine(exc);
            }
        }

        private static string FormatFromDictionary(string formatString, Dictionary<string, object> valueDict)
        {
            int i = 0;
            var newFormatString = new StringBuilder(formatString);
            var values = new List<object>();
            foreach (dynamic tuple in valueDict)
            {
                newFormatString = newFormatString.Replace("{" + tuple.Key + "}", "{" + i.ToString(CultureInfo.InvariantCulture) + "}");
                values.Add(tuple.Value);
                i++;
            }
            return String.Format(newFormatString.ToString(), values.ToArray());
        }
        private void OpenLoader()
        {
            switch (Properties.Settings.Default.LoaderDefaultBehavior)
            {
                case LoaderDefaultBehavior.OpenContent:
                    OpenContent();
                    break;
                case LoaderDefaultBehavior.OpenContentDirectory:
                    OpenContentDirectory();
                    break;
                case LoaderDefaultBehavior.OpenPage:
                    OpenPage();
                    break;
            }
        }

        private void OpenContent()
        {
            RunCommand(Properties.Settings.Default.OpenFileCommand,
                       Properties.Settings.Default.OpenFileCommandArgs);
        }

        private void OpenContentDirectory()
        {
            RunCommand(Properties.Settings.Default.OpenDirectoryCommand,
                       Properties.Settings.Default.OpenDirectoryCommandArgs);
        }

        private void OpenPage()
        {
            RunCommand(Properties.Settings.Default.OpenPageCommand,
                       Properties.Settings.Default.OpenPageCommandArgs);
        }

        #endregion

        #region 各種ダイアログの表示

        private void ShowPreferences()
        {
            using (var preference = new PreferenceForm(parsers, redirects, seekers, pickers))
            {
                if (preference.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
                {
                    loadersDataGridView.RowTemplate.Height = decimal.ToInt32(Properties.Settings.Default.LoaderRowSize);
                }
            }
        }

        private void ShowAboutBox()
        {
            using (var aboutBox = new CrawlerAboutBox())
                aboutBox.ShowDialog(this);
        }

        #endregion

        // ローダーの情報をクリップボードにコピーする
        private void CopyLoader(LoaderCopyProperties loaderCopyProperties)
        {
            var copyValue = new StringBuilder(loadersDataGridView.SelectedRows.Count * 100);
            var list = new List<string>(3);
            foreach (var loader in loadersDataGridView.SelectedRows.OfType<DataGridViewRow>().Select(r => r.DataBoundItem as ILoader))
            {
                list.Clear();
                if (loaderCopyProperties.HasFlag(LoaderCopyProperties.Title))
                    list.Add(loader.Title);
                if (loaderCopyProperties.HasFlag(LoaderCopyProperties.Uri))
                    list.Add(loader.Uri);
                if (loaderCopyProperties.HasFlag(LoaderCopyProperties.ContentUri))
                    list.Add(loader.ContentUri);
                if (list.Count > 0)
                    copyValue.AppendLine(string.Join(" ", list.ToArray()));
            }
            Clipboard.SetText(copyValue.ToString());
        }

        // DataGridViewに表示されている項目の数をステータスバーに表示する
        private void ListCount(ToolStripStatusLabel toolStripStatusLabel, int count)
        {
            if (InvokeRequired)
                Invoke(new Action(() => { toolStripStatusLabel.Text = string.Format(Properties.Strings.ItemsCountFormat, count); }));
            else
                toolStripStatusLabel.Text = string.Format(Properties.Strings.ItemsCountFormat, count);
        }

        private void DataGridViewError(DataGridViewDataErrorEventArgs e)
        {
            Debug.WriteLine(e.Exception);
            e.Cancel = true;
            e.ThrowException = false;
            Trace.TraceError(e.Exception.Message);
        }

        #region 出力

        private void OutputEventTypeFilter()
        {
            var item = outputEventTypeFilterToolStripBindingComboBox.SelectedItem;
            if (item is KeyValuePair<string, Enum>)
            {
                var type = (KeyValuePair<string, Enum>)item;
                tracer.Filter = "EventType = '" + (int)(TraceEventType)type.Value + "'";
            }
            else
            {
                tracer.RemoveFilter();
            }
        }

        private void OutputRemoveFilter()
        {
            outputEventTypeFilterToolStripBindingComboBox.SelectedIndex = -1;
            outputEventTypeFilterToolStripBindingComboBox.SelectedIndex = -1;
        }

        private void OutputCaution()
        {
            lock (outputCautionTimer)
                outputCautionTimer.Tag = 13;
        }

        private void OutputCaution(Timer timer)
        {
            if (timer != null)
                lock (timer)
                    if (timer.Tag is int)
                        if ((int)timer.Tag > 0)
                            switch ((int)timer.Tag % 3)
                            {
                                case 2:
                                    timer.Tag = ((int)timer.Tag) - 1;
                                    outputToolStrip.BackColor = Color.FromArgb(0xff, 0xcc, 0xcc);
                                    break;
                                case 1:
                                case 0:
                                    timer.Tag = ((int)timer.Tag) - 1;
                                    outputToolStrip.BackColor = SystemColors.Control;
                                    break;
                            }
        }

        private void CopyOutput(bool detail)
        {
            Clipboard.SetText(
                string.Join(Environment.NewLine,
                    outputDataGridView.SelectedRows
                        .Cast<DataGridViewRow>()
                        .Select(r => r.DataBoundItem as TraceData)
                        .Where(d => d != null)
                        .Select(d => d.GetText(detail))
                        .ToArray()));
        }

        private void SaveOutput(bool detail)
        {
            if (outputSaveFileDialog.ShowDialog(this) == DialogResult.OK)
                using (var writer = new StreamWriter(outputSaveFileDialog.FileName, false))
                    WriteOutput(writer, outputSaveFileDialog.FileName.EndsWith(".xml"), detail);
        }

        private void WriteOutput(TextWriter writer, bool xml, bool detail)
        {
            if (xml)
            {
                writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
                writer.WriteLine("<TraceData>");
            }
            foreach (var line in outputDataGridView.SelectedRows
                .Cast<DataGridViewRow>()
                .Select(r => r.DataBoundItem as TraceData)
                .Where(d => d != null)
                .Select(d => xml ? d.GetXml(detail) : d.GetText(detail)))
                writer.WriteLine(line);

            if (xml)
                writer.WriteLine("</TraceData>");
        }

        #endregion

        #region イベント

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            if (e.Cancel != true)
            {
                loaders.SaveList();
                anchors.SaveList();
                /*TODO:
                seekers.Save();
                redirects.Save();
                parsers.Save();
                pickers.Save();
                //*/
                Properties.Settings.Default.Save();
            }
        }

        private void RemoveAnchorToolStripButtonClick(object sender, EventArgs e)
        {
            RemoveAnchor();
        }

        private void AddLoaderToolStripButtonClick(object sender, EventArgs e)
        {
            AddLoader();
        }

        private void PreferencesToolStripMenuItemClick(object sender, EventArgs e)
        {
            ShowPreferences();
        }

        private void ReloadLoaderToolStripButtonClick(object sender, EventArgs e)
        {
            ReloadLoader();
        }

        private void RemoveLoaderToolStripButtonClick(object sender, EventArgs e)
        {
            RemoveLoader();
        }

        private void RemoveAnchorToolStripMenuItemClick(object sender, EventArgs e)
        {
            RemoveAnchor();
        }

        private void AddLoaderToolStripMenuItemClick(object sender, EventArgs e)
        {
            AddLoader();
        }

        private void RemoveLoaderToolStripMenuItemClick(object sender, EventArgs e)
        {
            RemoveLoader();
        }

        private void ReloadLoaderToolStripMenuItemClick(object sender, EventArgs e)
        {
            ReloadLoader();
        }

        private void AboutCrawlerToolStripMenuItemClick(object sender, EventArgs e)
        {
#if DEBUG
            Trace.TraceInformation("About Crawler");
#endif
            ShowAboutBox();
        }

        private void OpenLoaderPageToolStripButtonClick(object sender, EventArgs e)
        {
            OpenPage();
        }

        private void OpenLoaderContentToolStripButtonClick(object sender, EventArgs e)
        {
            OpenContent();
        }

        private void OpenLoaderContentDirectoryToolStripButtonClick(object sender, EventArgs e)
        {
            OpenContentDirectory();
        }

        private void CrawlerNotifierDoubleClick(object sender, EventArgs e)
        {
            Show();
            LoadWindowSize();
        }

        private void ExitNotifierContextMenuItemClick(object sender, EventArgs e)
        {
            Exit();
        }

        private void ExitToolStripMenuItemClick(object sender, EventArgs e)
        {
            Exit();
        }

        private void LoadersDataGridViewCellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            OpenLoader();
        }

        private void OutputEventTypeFilterToolStripBindingComboBoxSelectedIndexChanged(object sender, EventArgs e)
        {
            OutputEventTypeFilter();
        }

        private void OutputCautionTimerTick(object sender, EventArgs e)
        {
            OutputCaution(sender as Timer);
        }

        private void TracerListChanged(object sender, ListChangedEventArgs e)
        {
            switch (e.ListChangedType)
            {
                case ListChangedType.ItemAdded:
                case ListChangedType.ItemChanged:
                case ListChangedType.ItemDeleted:
                    OutputCaution();
                    outputDataGridView.Invalidate();
                    break;
            }
        }

        private void RemveOutputEventTypeFilterToolStripButtonClick(object sender, EventArgs e)
        {
            OutputRemoveFilter();
        }

        private void DataGridViewDataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            DataGridViewError(e);
        }

        private void PickersPluginListMenuItemClicked(object sender, PluginListMenuItemEventArgs e)
        {
            AddAnchor(e.Plugin);
        }

        private void OpenLoaderPageToolStripMenuItemClick(object sender, EventArgs e)
        {
            OpenPage();
        }

        private void OpenLoaderContentToolStripMenuItemClick(object sender, EventArgs e)
        {
            OpenContent();
        }

        private void OpenLoaderDirectoryToolStripMenuItemClick(object sender, EventArgs e)
        {
            OpenContentDirectory();
        }

        private void CopyTitlesToolStripMenuItemClick(object sender, EventArgs e)
        {
            CopyLoader(LoaderCopyProperties.Title);
        }

        private void CopyUrIsToolStripMenuItemClick(object sender, EventArgs e)
        {
            CopyLoader(LoaderCopyProperties.Uri);
        }

        private void CopyContentUrIsToolStripMenuItemClick(object sender, EventArgs e)
        {
            CopyLoader(LoaderCopyProperties.ContentUri);
        }

        private void CopyTitlesUriToolStripMenuItemClick(object sender, EventArgs e)
        {
            CopyLoader(LoaderCopyProperties.Title | LoaderCopyProperties.Uri);
        }

        private void CopyTitlesContentUriToolStripMenuItemClick(object sender, EventArgs e)
        {
            CopyLoader(LoaderCopyProperties.Title | LoaderCopyProperties.ContentUri);
        }

        private void CopyTitlesUrIsContentUrIsToolStripMenuItemClick(object sender, EventArgs e)
        {
            CopyLoader(LoaderCopyProperties.Title | LoaderCopyProperties.Uri | LoaderCopyProperties.ContentUri);
        }

        private void CopyOutputsToolStripMenuItemClick(object sender, EventArgs e)
        {
            CopyOutput(false);
        }

        private void CopyOutputsdetailToolStripMenuItemClick(object sender, EventArgs e)
        {
            CopyOutput(true);
        }

        private void SaveToFileToolStripMenuItemClick(object sender, EventArgs e)
        {
            SaveOutput(false);
        }

        private void SaveToFiledetailToolStripMenuItemClick(object sender, EventArgs e)
        {
            SaveOutput(true);
        }

        #endregion

        private void AnchorsListChanged(object sender, ListChangedEventArgs e)
        {
            ListCount(anchorsCountToolStripStatusLabel, anchors.Count);
        }

        private void LoadersListChanged(object sender, ListChangedEventArgs e)
        {
            ListCount(loadersCountToolStripStatusLabel, loaders.Count);
        }
    }
}